package com.gwt.dispatcher.dispatcher.server.services;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.gwt.dispatcher.dispatcher.server.ActionHandler;
import com.gwt.dispatcher.dispatcher.server.ActionHandlerManager;
import com.gwt.dispatcher.dispatcher.shared.Action;
import com.gwt.dispatcher.dispatcher.shared.Result;
import com.gwt.dispatcher.dispatcher.shared.exceptions.ActionHandlerException;
import com.gwt.dispatcher.dispatcher.shared.exceptions.DispatchException;
import com.gwt.dispatcher.dispatcher.shared.exceptions.NoActionHandlerFoundException;
import com.gwt.dispatcher.dispatcher.shared.exceptions.UnknownApplicationException;
import com.gwt.dispatcher.dispatcher.shared.services.ServerDispatcher;

/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class ServerDispatcherServiceImpl extends RemoteServiceServlet implements ServerDispatcher {

  public <T extends Result> T execute(Action<T> action) throws ActionHandlerException,
  DispatchException, UnknownApplicationException {

    // Logging Context handled by the Transaction Manager filter now
    // LoggingContext.enter();

    try {
      T result = null;
      ActionHandler<Action<T>, T> handler = null;
      long startTime = 0;

      try {
        handler = getHandler(action);

        startTime = System.currentTimeMillis();

        result = dispatch(action, handler);
      } catch (DispatchException e) {
        System.out.println("user exception thrown " + e.getMessage());
        throw e;
      } catch (NoActionHandlerFoundException e) {
        System.out.println("user exception thrown " + e.getMessage());
        throw e;
      } catch (Throwable t) {
        System.out.println("unexpected exception thrown " + t.getMessage());
        throw new UnknownApplicationException(t);
      } finally {
        long elapsedMsec = (startTime == 0) ? 0 : System.currentTimeMillis() - startTime;
        onBeforeReturn(action, handler, result, elapsedMsec);
      }

      return result;
    } finally {
      // LoggingContext.exit();
    }
  }

  /**
   * Log before return the handler's result.
   * 
   * @param <T>
   * @param action
   * @param handler
   * @param result
   * @param elapsedMsec
   */
  public <T extends Result> void onBeforeReturn(Action<T> action,
      ActionHandler<Action<T>, T> handler, Result result, long elapsedMsec) {
    String handlerName = (handler != null) ? handler.getClass().getName() : "";
    System.out.println(" <= request for action " + action.getClass().getName() + ", handler "
        + handlerName + " (" + elapsedMsec + " msec)");
  }

  /**
   * Pass the control to the handler
   * 
   * @param <T>
   * @param action
   * @param handler
   * @return
   * @throws NoActionHandlerFoundException
   * @throws SNMException
   */
  public <T extends Result> T dispatch(Action<T> action, ActionHandler<Action<T>, T> handler)
      throws NoActionHandlerFoundException, DispatchException {

    return handler.execute(action);
  }

  /**
   * Find the appropriate ActionHandler
   * 
   * @param <T>
   * @param action
   * @return
   * @throws NoActionHandlerFoundException
   */
  public <T extends Result> ActionHandler<Action<T>, T> getHandler(Action<T> action)
      throws NoActionHandlerFoundException {

    ActionHandler<Action<T>, T> handler = ActionHandlerManager.getMap().getHandler(action);

    if (handler == null) {
      String error =
          "Server side error. No Action Handler was found for action " + action.getClass();
      throw new NoActionHandlerFoundException(error);
    }

    handler.setThreadLocalRequest(this.getThreadLocalRequest());
    handler.setThreadLocalResponse(this.getThreadLocalResponse());

    return handler;
  }

  @Override
  // See
  // http://google-web-toolkit.googlecode.com/svn/javadoc/2.1/com/google/gwt/user/server/rpc/RemoteServiceServlet.html
  protected void checkPermutationStrongName() throws SecurityException {
    return;
  }

}
